﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Direction = LowLevelGraphics.Filter.Contours.Direction;
using System.Drawing;

using LowLevelGraphics.Drawing;
using LowLevelGraphics.Filter.Contours;

//TODO: test
//TODO: decide if this really is a filter or an extractor, because nothing is drawn at the moment.

namespace LowLevelGraphics.Filter
{
    /// <summary>
    /// 
    /// </summary>
    public class MarchingSquares : BaseImageFilter
    {
        // fields

        private byte[] m_aData;

        private int m_nWidth;

        private int m_nHeight;

        // constructors

        /// <summary>
        /// 
        /// </summary>
        public MarchingSquares()
        {
            m_aData = new byte[1024*1024];
            for (int i = 0; i < m_aData.Length; i++)
            {
                m_aData[i] = (byte)new Random().Next(0, 255);
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="MarchingSquares"/> class.
        /// </summary>
        /// <param name="width">The width.</param>
        /// <param name="height">The height.</param>
        /// <param name="_aData">The data.</param>
        public MarchingSquares(int width, int height, byte[] _aData)
        {
            this.m_nWidth = width;
            this.m_nHeight = height;
            this.m_aData = _aData;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="MarchingSquares"/> class.
        /// </summary>
        /// <param name="_marchingSquares">The _marching squares.</param>
        internal MarchingSquares(MarchingSquares _marchingSquares)
        {

        }

        /// <summary>
        /// return the width of the data matrix over which this object is operating
        /// </summary>
        /// <returns></returns>
        public int Width
        {
            get { return m_nWidth; }
        }

        /// <summary>
        /// Return the height of the data matrix over which this object is operating
        /// </summary>
        /// <returns></returns>
        public int Height
        {
            get { return m_nHeight; }
        }

        /// <summary>
        /// return the data matrix over which this object is operating
        /// </summary>
        /// <returns></returns>
        public byte[] GetData()
        {
            return m_aData;
        }

        /// <summary>
        /// Execute this Filter
        /// A Debug Statement is written if a concrete Filter is called
        /// </summary>
        /// <param name="_bitmap"></param>
        /// <returns></returns>
        public override UnsafeBitmap Execute(UnsafeBitmap _bitmap)
        {
            if (m_nWidth == 0 && m_nHeight == 0)
            {
                m_nWidth = _bitmap.Width; 
                m_nHeight = _bitmap.Height;
            }
            Path path = IdentifyPerimeter();
            UnsafeGraphics g = UnsafeGraphics.FromImage(_bitmap);
            g.DrawContourPath(Pens.Blue, path.Origin, path.Directions);
            return _bitmap;
        }

        // TODO could be made more efficient by accumulating value during movement
        /// <summary>
        /// Identifies the perimeter.
        /// Finds the perimeter between a set of zero and non-zero values which
        /// begins at the specified data element. If no initial point is known,
        /// consider using the convenience method supplied. The paths returned by
        /// this method are always closed.
        /// </summary>
        /// <param name="initialX">the column of the data matrix at which to start tracing the perimeter</param>
        /// <param name="initialY">the row of the data matrix at which to start tracing the perimeter</param>
        /// <returns>a closed, anti-clockwise path that is a perimeter of between a set of zero and non-zero values in the data.</returns>
        public Path IdentifyPerimeter(int initialX, int initialY)
        {
            if (initialX < 0) initialX = 0;
            if (initialX > m_nWidth) initialX = m_nWidth;
            if (initialY < 0) initialY = 0;
            if (initialY > m_nHeight) initialY = m_nHeight;

            int initialValue = Value(initialX, initialY);
            if (initialValue == 0 || initialValue == 15)
                throw new ArgumentException(String.Format("Supplied initial coordinates (%d, %d) do not lie on a perimeter.", initialX, initialY));

            List<LowLevelGraphics.Filter.Contours.Direction> directions = new List<LowLevelGraphics.Filter.Contours.Direction>();

            int x = initialX;
            int y = initialY;
            LowLevelGraphics.Filter.Contours.Direction previous = new LowLevelGraphics.Filter.Contours.Direction();

            do
            {
                LowLevelGraphics.Filter.Contours.Direction direction;
                switch (Value(x, y))
                {
                    case 1: direction = LowLevelGraphics.Filter.Contours.Direction.N; break;
                    case 2: direction = LowLevelGraphics.Filter.Contours.Direction.E; break;
                    case 3: direction = LowLevelGraphics.Filter.Contours.Direction.E; break;
                    case 4: direction = LowLevelGraphics.Filter.Contours.Direction.W; break;
                    case 5: direction = LowLevelGraphics.Filter.Contours.Direction.N; break;
                    case 6: direction = previous == LowLevelGraphics.Filter.Contours.Direction.N ? LowLevelGraphics.Filter.Contours.Direction.W : LowLevelGraphics.Filter.Contours.Direction.E; break;
                    case 7: direction = LowLevelGraphics.Filter.Contours.Direction.E; break;
                    case 8: direction = LowLevelGraphics.Filter.Contours.Direction.S; break;
                    case 9: direction = previous == LowLevelGraphics.Filter.Contours.Direction.E ? LowLevelGraphics.Filter.Contours.Direction.N : LowLevelGraphics.Filter.Contours.Direction.S; break;
                    case 10: direction = LowLevelGraphics.Filter.Contours.Direction.S; break;
                    case 11: direction = LowLevelGraphics.Filter.Contours.Direction.S; break;
                    case 12: direction = LowLevelGraphics.Filter.Contours.Direction.W; break;
                    case 13: direction = LowLevelGraphics.Filter.Contours.Direction.N; break;
                    case 14: direction = LowLevelGraphics.Filter.Contours.Direction.W; break;
                    default: throw new Exception("Illegal state exception"); //Illegalstateexception
                }
                directions.Add(direction);
                x += direction.screenX;
                y += direction.screenY; // accomodate change of basis
                previous = direction;
            } while (x != initialX || y != initialY);

            return new Path(initialX, -initialY, directions.ToArray());
        }

        /// <summary>
        /// A convenience method that locates at least one perimeter in the data with
        /// which this object was constructed. If there is no perimeter (ie. if all
        /// elements of the supplied array are identically zero) then null is
        /// returned.
        /// </summary>
        /// <returns>return a perimeter path obtained from the data, or null</returns>
        public Path IdentifyPerimeter()
        {
            int nSize = m_nWidth * m_nHeight;
            for (int i = 0; i < nSize; i++)
            {
                if (m_aData[i] != 0)
                {
                    return IdentifyPerimeter(i % m_nWidth, i / m_nWidth);
                }
            }
            return null;
        }

        // private utility methods

        /// <summary>
        /// Values the specified x.
        /// </summary>
        /// <param name="x">The x.</param>
        /// <param name="y">The y.</param>
        /// <returns></returns>
        private int Value(int x, int y)
        {
            int sum = 0;
            if (IsSet(x, y)) sum |= 1;
            if (IsSet(x + 1, y)) sum |= 2;
            if (IsSet(x, y + 1)) sum |= 4;
            if (IsSet(x + 1, y + 1)) sum |= 8;
            return sum;
        }

        /// <summary>
        /// Determines whether the specified x is set.
        /// </summary>
        /// <param name="x">The x.</param>
        /// <param name="y">The y.</param>
        /// <returns>
        /// 	<c>true</c> if the specified x is set; otherwise, <c>false</c>.
        /// </returns>
        private bool IsSet(int x, int y)
        {
            return x <= 0 || x > m_nWidth || y <= 0 || y > m_nHeight ?
                false :
                m_aData[(y - 1) * m_nWidth + (x - 1)] != 0;
        }

        /// <summary>
        /// This is used to clone a filter, each filter must implement it's own clone functionality
        /// </summary>
        /// <returns></returns>
        public override object Clone()
        {
            return new MarchingSquares(this);
        }

    }
}
